package org.eclipse.epsilon.emc.jdt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.execute.introspection.AbstractPropertyGetter;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BlockComment;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.LineComment;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jface.text.Document;

/**
 * This class retrieves the value of properties of JDT model element
 * 
 * @author Cheng Yun
 * 
 */
public class JdtPropertyGetter extends AbstractPropertyGetter {
	@SuppressWarnings("unchecked")
	@Override
	public Object invoke(Object object, String property)
			throws EolRuntimeException {

		if (object instanceof IJavaProject) {// JavaProjact
			IJavaProject javaProject = (IJavaProject)object;
			if (property.equals("name")) {// JavaProjact.name
				return javaProject.getProject().getName();
			}

			if (property.equals("packages")) {// JavaProjact.packages
				try {
					return JdtManipulator.getIPackageFragments(
							javaProject, true);
				} catch (CoreException e) {
					e.printStackTrace();
				}
			}
		}

		if (object instanceof IPackageFragment) {// Package
			IPackageFragment packageFragment = (IPackageFragment)object;
			if (property.equals("name")) {// Package.name
				return packageFragment.getElementName();
			}
			
			if (property.equals("javaFiles")) {
				try {
					// returns a list of CompilationUnits
					return JdtManipulator
							.getCompilationUnits(packageFragment);
				} catch (CoreException e) {
					e.printStackTrace();
				}
			}
		}
		
		if (object instanceof ASTNode) {//ASTNode
			ASTNode node = (ASTNode) object;
			if (property.equals("parent")) {// ASTNode.parent
				return node.getParent();
			}

			if (property.equals("type")) {// ASTNode.type
				return ASTNode.nodeClassForType(node.getNodeType());
			}
		}
		
		if (object instanceof CompilationUnit){// JavaFile
			CompilationUnit unit = (CompilationUnit)object;
			if (property.equals("name")){
				return unit.getJavaElement().getElementName();
			}
			
			if (property.equals("classes")){// JavaFile.classes
				return JdtManipulator.getTypeDeclarations(unit);
			}
			
			if (property.equals("enumerations")){// JavaFile.enumerations
				return JdtManipulator.getEnumerations(unit);
			}
			
			if (property.equals("annotations")){// JavaFile.annotations
				return JdtManipulator.getAnnotations(unit);
			}
			
			if (property.equals("classStatements")){ // JavaFile.classStatements
				return JdtManipulator.getTypeDeclarationStatements(unit);
			}
			
			if (property.equals("linesOfCode")){// JavaFile.linesOfCode
				Document doc;
				try {
					doc = new Document(((ICompilationUnit)unit.getJavaElement()).getSource());
					return doc.getNumberOfLines();
				} catch (JavaModelException e) {
					e.printStackTrace();
				}
			}
		}

		if (object instanceof TypeDeclaration) {// Class
			TypeDeclaration typeDeclaration = (TypeDeclaration) object;

			if (property.equals("name")) {// Class.name
				return typeDeclaration.getName().getFullyQualifiedName();
			}
			
			if (property.equals("simpleName")) {// Class.simpleName
				return typeDeclaration.getName();
			}

			if (property.equals("modifiers")) {// Class.modifiers
				int modifier = typeDeclaration.getModifiers();
				return getModifiers(modifier);
			}
			
			if (property.equals("blocks")){
				return JdtManipulator.getBlocks(typeDeclaration);
			}
					
			if (property.equals("statements")){
				Block [] blocks = JdtManipulator.getBlocks(typeDeclaration);
				List <Statement> statments = new ArrayList<Statement>();
				for(int i=0; i<blocks.length; i++){
					if(blocks[i]!=null){
						statments.addAll(blocks[i].statements());
					}
				}
				return statments;
			}
			
			if (property.equals("comments")){
				ASTNode temp = typeDeclaration;
        		while(!(ASTNode.nodeClassForType(temp.getNodeType()).toString()).equals("class org.eclipse.jdt.core.dom.CompilationUnit") ){
        			temp = temp.getParent();
        		}
				CompilationUnit unit= (CompilationUnit)temp;
				List<Comment> unitComments = unit.getCommentList();
				List<Comment> comments = new ArrayList<Comment>();
				for(Comment comment : unitComments){
					if(!(comment instanceof Javadoc)){
						comments.add(comment);
					}
				}
				return comments;
			}
			
			if (property.equals("lineComments")) {// Class.lineComments
				CompilationUnit unit= (CompilationUnit)(typeDeclaration.getParent());
				List<Comment> unitComments = unit.getCommentList();
				List<LineComment> lineComments = new ArrayList<LineComment>();
				for(Comment comment : unitComments){
					if(comment instanceof LineComment){
						lineComments.add((LineComment)comment);
					}
				}
				return lineComments;
			}

			if (property.equals("blockComments")) {// Class.blockComments
				CompilationUnit unit= (CompilationUnit)(typeDeclaration.getParent());
				List<Comment> unitComments = unit.getCommentList();
				List<BlockComment> blcokComments = new ArrayList<BlockComment>();
				for(Comment comment : unitComments){
					if(comment instanceof BlockComment){
						blcokComments.add((BlockComment)comment);
					}
				}
				return blcokComments;
			}
		}

		if (object instanceof FieldDeclaration) {// Field
			FieldDeclaration field = (FieldDeclaration) object;
			if (property.equals("name")) {// Field.name
				return field;
			}

			if (property.equals("modifiers")) {// Field.modfier
				int modifier = field.getModifiers();
				return getModifiers(modifier);
			}
		}

		if (object instanceof MethodDeclaration) {// Method
			MethodDeclaration method = (MethodDeclaration) object;
			if (property.equals("name")) {// Method.name
				return method.getName().getFullyQualifiedName();
			}
			
			if (property.equals("simpleName")) {// Method.simpleName
				return method.getName();
			}

			if (property.equals("modifiers")) {// Method.modifiers
				System.out.println("Modifier:" + method.getModifiers());
				int modifier = method.getModifiers();
				return getModifiers(modifier);
			}

			if (property.equals("returnType")) {// Method.returnType
				return method.getReturnType2();
			}
	
			if (property.equals("statements")){
				if(method.getBody()!=null){
					return method.getBody().statements();
				}
				return null;
			}

			if (property.equals("returnStatements")){// Method.returnStatements
				return JdtManipulator.getReturnStatements(method);
			}

			if (property.equals("lineComments")) {// Method.lineComments
				return JdtManipulator.getLineComments(method);
			}

			if (property.equals("blockComments")) {// Method.blockComments
				return JdtManipulator.getBlockComments(method);
			}
		}
		
		if (object instanceof VariableDeclarationStatement) {// Variables
			VariableDeclarationStatement variable = (VariableDeclarationStatement) object;		
			if (property.equals("modifiers")) {
				int modifier = variable.getModifiers();
				return getModifiers(modifier);
			}
		}
		
		if (object instanceof VariableDeclarationExpression){
			VariableDeclarationExpression varExpression = (VariableDeclarationExpression)object;
			if (property.equals("modifiers")) {
				int modifier = varExpression.getModifiers();
				return getModifiers(modifier);
			}
		}

		if (object instanceof VariableDeclarationFragment) {
			VariableDeclarationFragment vaFragment = (VariableDeclarationFragment) object;
			if (property.equals("name")) {
				return vaFragment.getName().getFullyQualifiedName();
			}
			
			if (property.equals("simpleName")) {// .simpleName
				return vaFragment.getName();
			}
		}

		if (object instanceof SingleVariableDeclaration) {//SingleVariableDeclaration
			System.out.println("SingleVariableDeclaration");
			SingleVariableDeclaration sinVariable = (SingleVariableDeclaration) object;
			if (property.equals("Name")) {
				return sinVariable.getName().getFullyQualifiedName();
			}
				
			if (property.equals("simpleName")) {// .simpleName
				return sinVariable.getName();
			}

			if (property.equals("modifiers")) {
				int modifier = sinVariable.getModifiers();
				return getModifiers(modifier);
			}
		}

		if ((object instanceof Statement) || (object instanceof Expression)
				|| (object instanceof FieldDeclaration)
				|| (object instanceof MethodDeclaration)) {
			ASTNode node = (ASTNode) object;
			
			if (property.equals("initializers")){
				return JdtManipulator.getInitializers(node);
			}
			
			//retrieve statement or expression properties
			return getStatementsOrExpressions(node, property);
		}
		
		return null;
	}

	/**
	 * This method calls related methods in the JdtManipulator to retrieve the
	 * requested Statement or Expression AST node.
	 * 
	 * @param node
	 *            ASTNode
	 * @param property
	 *            property
	 * @return requested statement AST node
	 */
	private Object getStatementsOrExpressions(ASTNode node, String property){
		// Type properties
		if (property.equals("arrayTypes")){
			return JdtManipulator.getArrayTypes(node);
		}
		
		if (property.equals("parameterizedTypes")){
			return JdtManipulator.getParameterizedTypes(node);
		}
		
		if (property.equals("primitiveTypes")){
			return JdtManipulator.getPrimitiveTypes(node);
		}
		
		if (property.equals("qualifiedTypes")){
			return JdtManipulator.getQualifiedTypes(node);
		}
		
		if (property.equals("simpleTypes")){
			return JdtManipulator.getSimpleTypes(node);
		}
		
		if (property.equals("unionTypes")){
			return JdtManipulator.getUnionTypes(node);
		}
		
		if (property.equals("wildcardTypes")){
			return JdtManipulator.getWildcardTypes(node);
		}
		
		// Statement properties
		if (property.equals("ifStatements")) {
			return JdtManipulator.getIfStatements(node);
		}
		
		if (property.equals("whileStatements")){
			return JdtManipulator.getWhileStatements(node);
		}
		
		if (property.equals("doStatements")) {
			return JdtManipulator.getDoStatements(node);
		}
		
		if (property.equals("switchStatements")){
			return JdtManipulator.getSwitchStatements(node);
		}
		
		if (property.equals("forStatements")){
			return JdtManipulator.getForStatements(node);
		}
		
		if (property.equals("enhancedForStatements")){
			return JdtManipulator.getEnhancedForStatements(node);
		}
		
		if (property.equals("tryStatements")){
			return JdtManipulator.getTryStatements(node);
		}
		
		if (property.equals("synchronizedStatements")){
			return JdtManipulator.getSynchronizedStatements(node);
		}
		
		if (property.equals("variableStatements")) {
			return JdtManipulator.getVariableDeclarationStatements(node);
		}
		
		if (property.equals("assertStatements")){
			return JdtManipulator.getAssertStatements(node);
		}
		
		if (property.equals("throwStatements")){
			return JdtManipulator.getThrowStatements(node);
		}
		
		if (property.equals("switchCases")){
			return JdtManipulator.getSwitchCases(node);
		}
		
		if (property.equals("blocks")){
			return JdtManipulator.getBlocks(node);
		}
		
		if (property.equals("breakStatements")){
			return JdtManipulator.getBreakStatements(node);
		}
		
		if (property.equals("continueStatements")){
			return JdtManipulator.getContinueStatements(node);
		}
		
		if (property.equals("constructorInvocations")){
			return JdtManipulator.getConstructorInvocations(node);
		}
		
		if (property.equals("emptyStatements")){
			return JdtManipulator.getEmptyStatements(node);
		}
		
		if (property.equals("superConstructorInvocations")){
			return JdtManipulator.getSuperConstructorInvocations(node);
		}
		
		if (property.equals("labeledStatements")){
			return JdtManipulator.getLabeledStatements(node);
		}
		
		if (property.equals("expressionStatements")){
			return JdtManipulator.getExpressionStatements(node);
		}
		
		// Expression properties
		if (property.equals("arrayAccesses")){
			return JdtManipulator.getArrayAccesses(node);
		}
		
		if (property.equals("arrayCreations")){
			return JdtManipulator.getArrayCreations(node);
		}
		
		if (property.equals("arrayInitializers")){
			return JdtManipulator.getArrayInitializers(node);
		}
		
		if (property.equals("assignments")){
			return JdtManipulator.getAssignments(node);
		}
		
		if (property.equals("booleanLiterals")){
			return JdtManipulator.getBooleanLiterals(node);
		}
		
		if (property.equals("castExpressions")){
			return JdtManipulator.getCastExpressions(node);
		}
		
		if (property.equals("characterLiterals")){
			return JdtManipulator.getCharacterLiterals(node);
		}
		
		if (property.equals("classInstanceCreations")){
			return JdtManipulator.getClassInstanceCreations(node);
		}
		
		if (property.equals("conditionalExpressions")){
			return JdtManipulator.getConditionalExpressions(node);
		}
		
		if (property.equals("fieldAccesses")){
			return JdtManipulator.getFieldAccesses(node);
		}
		
		if (property.equals("instanceofExpressions")){
			return JdtManipulator.getInstanceofExpressions(node);
		}
		
		if (property.equals("methodInvocations")){
			return JdtManipulator.getMethodInvocations(node);
		}
		
		if (property.equals("numberLiterals")){
			return JdtManipulator.getNumberLiterals(node);
		}
		
		if (property.equals("parenthesizedExpressions")){
			return JdtManipulator.getParenthesizedExpressions(node);
		}
		
		if (property.equals("postfixExpressions")){
			return JdtManipulator.getPostfixExpressions(node);
		}
		
		if (property.equals("prefixExpressions")){
			return JdtManipulator.getPrefixExpressions(node);
		}
		
		if (property.equals("superFieldAccesses")){
			return JdtManipulator.getSuperFieldAccesses(node);
		}
		
		if (property.equals("superMethodInvocations")){
			return JdtManipulator.getSuperMethodInvocations(node);
		}
		
		if (property.equals("thisExpressions")){
			return JdtManipulator.getThisExpressions(node);
		}
		
		if (property.equals("variableDeclarationExpressions")){
			return JdtManipulator.getVariableDeclarationExpressions(node);
		}
		
		if (property.equals("qualifiedNames")){
			return JdtManipulator.getQualifiedNames(node);
		}
		
		if (property.equals("simpleNames")){
			return JdtManipulator.getSimpleNames(node);
		}
		
		if (property.equals("stringLiterals")){
			return JdtManipulator.getStringLiterals(node);
		}
		
		if (property.equals("nullLiterals")){
			return JdtManipulator.getNullLiterals(node);
		}
		
		if (property.equals("infixExpressions")){
			return JdtManipulator.getInfixExpressions(node);
		}
		
		if (property.equals("typeLiterals")){
			return JdtManipulator.getTypeLiterals(node);
		}
		
		return null;
	}

	/**
	 * This method converts modifiers constants to String.
	 * <p>
	 * For example, if the modifier of a method is "public", you will get an
	 * integer 1 when you call MethodDeclaration.getModifiers(). By calling this
	 * method you can convert this constant to "public" string.
	 * 
	 * @param modifier
	 *            the constant of modifiers
	 * @return modifiers in String format
	 * @see org.eclipse.jdt.core.dom.Modifier
	 */
	private String [] getModifiers(int modifier) {
		HashMap<Integer, String> modifiers = new HashMap<Integer, String>();
		modifiers.put(0x0000, "");
		modifiers.put(0x0001, "public");
		modifiers.put(0x0002, "private");
		modifiers.put(0x0004, "protected");
		modifiers.put(0x0008, "static");
		modifiers.put(0x0010, "final");
		modifiers.put(0x0400, "abstract");
		modifiers.put(0x0020, "synchronized");
		modifiers.put(0x0100, "native");
		modifiers.put(0x0800, "strictf");
		modifiers.put(0x0040, "volatile");
		Integer[] key = modifiers.keySet().toArray(
				new Integer[modifiers.keySet().size()]);
		String [] returnedModifiers;
		// if the method or class only has one modifier, such as
		// "public void doSomething()"
		if (modifiers.containsKey(modifier)) {
			returnedModifiers = new String[]{modifiers.get(modifier)};
			return returnedModifiers;
		}

		// if the method or class only has two modifiers, such as
		// "public static void doSomething()"
		for (int i = 0; i < key.length; i++) {
			for (int j = i + 1; j < key.length; j++) {
				if ((key[i] + key[j]) == modifier) {
					returnedModifiers = new String[] { modifiers.get(key[i]),
							modifiers.get(key[j]) };
					return returnedModifiers;

				}
			}
		}

		// if the method or class only has three modifiers, such as
		// "public static final void doSomething()"
		for (int i = 0; i < key.length; i++) {
			for (int j = i + 1; j < key.length; j++) {
				for (int k = j + 1; k < key.length; k++) {
					if ((key[i] + key[j] + key[k]) == modifier) {
						returnedModifiers = new String[] { modifiers.get(key[i]),
								modifiers.get(key[j]), modifiers.get(key[k])};
						return returnedModifiers;
					}
				}
			}
		}
		return null;
	}

}
